﻿using System;
using Microsoft.SPOT;

using GTM = Gadgeteer.Modules;

using Microsoft.SPOT.Hardware;

namespace Gadgeteer.Modules.GHIElectronics
{
    // -- CHANGE FOR MICRO FRAMEWORK 4.2 --
    // If you want to use Serial, SPI, or DaisyLink (which includes GTI.SoftwareI2C), you must do a few more steps
    // since these have been moved to separate assemblies for NETMF 4.2 (to reduce the minimum memory footprint of Gadgeteer)
    // 1) add a reference to the assembly (named Gadgeteer.[interfacename])
    // 2) in GadgeteerHardware.xml, uncomment the lines under <Assemblies> so that end user apps using this module also add a reference.

    /// <summary>
    /// An IR Receiver module for Microsoft .NET Gadgeteer
    /// </summary>
    /// /// <example>
    /// <para>The following example uses a <see cref="IR_Receiver"/> object to read which button was pressed on a remote. 
    /// First, we set up our event to notify us when a button has been pressed.
    /// Then, when a button has been pressed, we use the event to determine which button was pressed.
    /// </para>
    /// <code>
    /// using System;
    /// using System.Collections;
    /// using System.Threading;
    /// using Microsoft.SPOT;
    /// using Microsoft.SPOT.Presentation;
    /// using Microsoft.SPOT.Presentation.Controls;
    /// using Microsoft.SPOT.Presentation.Media;
    /// using Microsoft.SPOT.Touch;
    ///
    /// using Gadgeteer.Networking;
    /// using GT = Gadgeteer;
    /// using GTM = Gadgeteer.Modules;
    /// using Gadgeteer.Modules.GHIElectronics;
    ///
    /// namespace TestApp
    /// {
    ///     public partial class Program
    ///     {
    ///         void ProgramStarted()
    ///         {
    ///             irreceiver.IREvent += new IR_Receiver.IREventDelegate(irreceiver_IREvent);
    ///         }
    ///
    ///         void irreceiver_IREvent(object sender, IR_Receiver.IREventArgs e)
    ///         {
    ///             uint buttonPressed = e.Button;
    ///         }
    ///     }
    /// }
    /// </code>
    /// </example>
    public class IR_Receiver : GTM.Module
    {
        private static long last_tick;
        private static long bit_time;
        private static uint pattern;
        private static bool streaming;
        private static uint shiftBit;
        private static bool new_press = false;

        /// <summary>
        /// The protocol used for communication.
        /// </summary>
        public enum ReceiverType : byte
        {
            /// <summary>
            /// RC-5 protocol.
            /// </summary>
            RC5 = 1,
        }
        ReceiverType IRReceiverType;

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socketNumber">The socket that this module is plugged in to.</param>
        public IR_Receiver(int socketNumber)
        {
            // This finds the Socket instance from the user-specified socket number.  
            // This will generate user-friendly error messages if the socket is invalid.
            // If there is more than one socket on this module, then instead of "null" for the last parameter, 
            // put text that identifies the socket to the user (e.g. "S" if there is a socket type S)
            Socket socket = Socket.GetSocket(socketNumber, true, this, null);

            last_tick = DateTime.Now.Ticks;
            IRReceiverType = ReceiverType.RC5; // Only one supported at this time

            // This creates an GTI.InterruptInput interface. The interfaces under the GTI namespace provide easy ways to build common modules.
            // This also generates user-friendly error messages automatically, e.g. if the user chooses a socket incompatible with an interrupt input.
            this.input = new InterruptPort(socket.CpuPins[3], false, Port.ResistorMode.Disabled, Port.InterruptMode.InterruptEdgeBoth);//new GTI.InterruptInput(socket, GT.Socket.Pin.Three, GTI.GlitchFilterMode.On, GTI.ResistorMode.Disabled, GTI.InterruptMode.RisingAndFallingEdge, this);

            // This registers a handler for the interrupt event of the interrupt input (which is below)
            this.input.OnInterrupt += new NativeEventHandler(input_OnInterrupt); //new GTI.InterruptInput.InterruptEventHandler(this._input_Interrupt);
        }

        private InterruptPort input;

        void input_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            bit_time = time.Ticks - last_tick;
            last_tick = time.Ticks;
            switch (IRReceiverType)
            {
                case ReceiverType.RC5:
                    /* When testing revealed that the togglebit not work reliably with fast key action,
                     * hence this is resolved by testing for a bit timeout of 100 ms. This gives better results.*/
                    if (bit_time > 1000000) // 100 ms
                        new_press = true;
                    if (bit_time > 26670)  // 3 * halftime (half_bittime = 889 us)
                    {
                        bit_time = 0;
                        pattern = 0;
                        if (data2 == 0)
                        {
                            streaming = true;
                            shiftBit = 1;
                            pattern |= shiftBit;
                        }
                        else
                            streaming = false;
                        return;
                    }
                    if (streaming)
                    {
                        if (bit_time > 10668)  // = half_bittime * 1.2 (half_bittime = 889 us)
                        {
                            if (data2 == 0)
                                shiftBit = 1;
                            else
                                shiftBit = 0;
                            pattern <<= 1;
                            pattern |= shiftBit;
                        }
                        else
                        {
                            if (data2 == 0)
                            {
                                pattern <<= 1;
                                pattern |= shiftBit;
                            }
                        }
                        if ((pattern & 0x2000) > 0)  // 14 bits
                        {
                            if (new_press)
                            {
                                IREventArgs _args = new IREventArgs();
                                _args.Button = pattern & 0x3F;
                                _args.ReadTime = DateTime.Now;
                                OnIREvent(_args);
                                new_press = false;
                            }
                            pattern = 0;
                            bit_time = 0;
                            streaming = false;
                        }
                    }
                    break;
                default:
                    break;
            }
        }

        /// <summary>
        /// Represents the delegate that is used to handle the <see cref="IREvent"/>
        /// event.        
        /// </summary>
        /// <param name="sender">The module that sends the event.</param>
        /// <param name="e">Event information, including the button that was pressed and the time it was pressed.</param>
        public delegate void IREventDelegate(object sender, IREventArgs e);

        /// <summary>
        /// Raised when the module detects an IR signal.
        /// </summary>
        public event IREventDelegate IREvent;

        /// <summary>
        /// Class that holds information about when a button press was detexted.
        /// </summary>
        public class IREventArgs : EventArgs
        {
            /// <summary>
            /// The button what was pressed.
            /// </summary>
            public uint Button { get; set; }

            /// <summary>
            /// The time that the button was read.
            /// </summary>
            public DateTime ReadTime { get; set; }
        }

        private void OnIREvent(IREventArgs e)
        {
            if (IREvent != null)
            {
                IREvent(this, e);
            }
        }
    }
}